package com.dt.app.security.config;

import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.dt.app.api.sys.service.SysLoginLogService;
import com.dt.app.api.sys.service.impl.SysUserServiceImpl;
import com.dt.app.common.constant.ConstantCode;
import com.dt.app.common.utils.AesEncryptUtils;
import com.dt.app.common.utils.IpAddressUtils;
import com.dt.app.common.utils.ServletUtils;
import com.dt.app.exception.CmsPasswordException;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 自定义认证授权处理器
 * @author DT
 * @date 2021/6/5 10:34
 */
@Component
public class CmsUserAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private SysUserServiceImpl userDetailsService;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private SysLoginLogService sysLoginLogService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // 表单输入账号、密码
        String username = authentication.getName();
        String password = (String) authentication.getCredentials();
        // 账号是否被锁定
        String loginCountStr = stringRedisTemplate.opsForValue().get(ConstantCode.LOGIN_LOCK + username);
        if(loginCountStr != null && loginCountStr.length() > 1) {
            throw new CmsPasswordException("账户被锁定15分钟，请稍后登录!");
        }
        // 密码解密
        String newPassword = Objects.requireNonNull(AesEncryptUtils.desEncrypt(password)).trim();
        // 获取用户信息
        UserDetails user = userDetailsService.loadUserByUsername(username);
        // 匹配密码
        boolean flag = passwordEncoder.matches(newPassword, user.getPassword());
        if (!flag) {
            // 密码超过5次错误，用户冻结15分钟
            int lockCount = this.validLock(username) + 1;
            if(lockCount >= ConstantCode.LOCK_TIME) {
                Date expDate = DateUtils.addMinutes(new Date(), ConstantCode.LOCK_MINUTE);
                stringRedisTemplate.opsForValue().set(ConstantCode.LOGIN_LOCK + username, String.valueOf(expDate.getTime()),ConstantCode.LOCK_MINUTE, TimeUnit.MINUTES);
                // 记录账号锁定日志
                String ip = IpAddressUtils.getIpAddr(ServletUtils.getRequest());
                UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
                this.sysLoginLogService.saveLoginLog(ConstantCode.LOGINLOCK_STRING_KEY, username,userAgent,ip);
                throw new CmsPasswordException("您的账号已被冻结15分钟");
            }
            stringRedisTemplate.opsForValue().set(ConstantCode.LOGIN_LOCK + username, String.valueOf(lockCount), ConstantCode.LOCK_MINUTE, TimeUnit.MINUTES);
            throw new CmsPasswordException("用户密码校验错误，再输错"+(5 - lockCount)+"次该用户将被锁定15分钟");
        }
        // 检验账号状态
        if (!user.isAccountNonExpired()) {
            throw new AccountExpiredException("账号过期!");
        } else if (!user.isAccountNonLocked()) {
            throw new LockedException("账号被锁!");
        } else if(!user.isEnabled()) {
            throw new DisabledException("账号被禁用");
        }
        // 账号密码以及权限验证
        return new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return aClass.equals(UsernamePasswordAuthenticationToken.class);
    }

    protected int validLock(String username) {
        String loginCountStr = stringRedisTemplate.opsForValue().get(ConstantCode.LOGIN_LOCK + username);
        int loginCount = NumberUtils.toInt(loginCountStr,0);
        if(loginCountStr != null && loginCountStr.length() > 1) {
            if(System.currentTimeMillis() < Long.parseLong(loginCountStr)) {
                throw new CmsPasswordException("账户被锁定15分钟，请稍后登录!");
            }
        }
        return loginCount;
    }
}
